package com.yfan.utils;

import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.math.linearalgebra.ByteUtils;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.SecureRandom;
import java.security.Security;

/**
 * Sm4 国密算法
 */
public final class SM4CBCUtils {

	static {
		Security.addProvider(new BouncyCastleProvider());
	}

	private static final String ENCODING = "UTF-8";

	public static final String ALGORITHM_NAME = "SM4";
	// 加密算法/分组加密模式/分组填充方式
	// PKCS5Padding-以8个字节为一组进行分组加密
	// 定义分组加密模式使用：PKCS5Padding

	public static final String ALGORITHM_NAME_CBC_PADDING = "SM4/CBC/PKCS5Padding";
	// 128-32位16进制；256-64位16进制
	public static final int DEFAULT_KEY_SIZE = 128;

	public static final String SM4_KEY_STR = "深圳!@#筑龙$%^True&*?Lore8b6ba8d0-9986-44b2-8ac7-3d75bf7035bf";// 密钥

	public static final byte[] SM4_IV = new byte[]{(byte) 0xE2, (byte) 0xF4,
			0x16, (byte) 0xD8, 0x59, 0x0B, 0x0D, 0x0F, (byte) 0xB2,
			(byte) 0xA4, (byte) 0xC9, 0x7D, (byte) 0x9F, (byte) 0xAB,
			(byte) 0xDC, (byte) 0xEA}; // 向量

	/**
	 * 自动生成密钥
	 *
	 * @return
	 * @explain
	 */
	public static byte[] generateKey() throws Exception {
		return generateKey(SM4_KEY_STR);
	}


	/**
	 * 自动生成密钥
	 *
	 * @return
	 * @throws Exception
	 */
	public static String generateKeyString() throws Exception {
		return ByteUtils.toHexString(generateKey());
	}

	/**
	 * @return
	 * @throws Exception
	 * @explain
	 */
//    public static byte[] generateKey(int keySize) throws Exception {
//        KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM_NAME, BouncyCastleProvider.PROVIDER_NAME);
//        kg.init(keySize, new SecureRandom());
//        return kg.generateKey().getEncoded();
//    }
	private static byte[] generateKey(String keyStr) throws Exception {
		if (keyStr == null) {
			return new byte[0];
		}
		byte[] keyByteArray = keyStr.getBytes(ENCODING);
		byte[] keyArray = new byte[16];
		if (keyByteArray.length < 16) {
			KeyGenerator kgen = KeyGenerator.getInstance(ALGORITHM_NAME);
			kgen.init(DEFAULT_KEY_SIZE, new SecureRandom(keyByteArray));
			SecretKey key = kgen.generateKey();
			keyArray = key.getEncoded();
		} else {
			System.arraycopy(keyByteArray, 0, keyArray, 0, 16);
		}
		return keyArray;
	}

	/**
	 * 加密模式之CBC
	 *
	 * @param algorithmName
	 * @param mode
	 * @param key
	 * @return
	 * @throws Exception
	 */
	private static Cipher generateCbcCipher(String algorithmName, int mode, byte[] key) throws Exception {
		Cipher cipher = Cipher.getInstance(algorithmName, BouncyCastleProvider.PROVIDER_NAME);
		Key sm4Key = new SecretKeySpec(key, ALGORITHM_NAME);
		cipher.init(mode, sm4Key, generateIV());
		return cipher;
	}

	/**
	 * 生成iv
	 *
	 * @return
	 * @throws Exception
	 */
	private static AlgorithmParameters generateIV() throws Exception {
		//iv 为一个 16 字节的数组，这里采用和 iOS 端一样的构造方法，数据全为0
//        byte[] iv = new byte[16];
//        Arrays.fill(iv, (byte) 0x00);
		AlgorithmParameters params = AlgorithmParameters.getInstance(ALGORITHM_NAME);
		params.init(new IvParameterSpec(SM4_IV));
		return params;
	}


	/**
	 * sm4加密
	 *
	 * @param hexKey   16进制密钥（忽略大小写）
	 * @param paramStr 待加密字符串
	 * @return 返回16进制的加密字符串
	 * @throws Exception
	 * @explain 加密模式：CBC
	 */
	public static String encrypt(String hexKey, String paramStr) throws Exception {
		String result = "";
		// 16进制字符串-->byte[]
		byte[] keyData = ByteUtils.fromHexString(hexKey);
		// String-->byte[]
		byte[] srcData = paramStr.getBytes(ENCODING);
		// 加密后的数组
		byte[] cipherArray = encryptCbcPadding(keyData, srcData);

		// byte[]-->hexString
		result = ByteUtils.toHexString(cipherArray);
		return result;
	}

	/**
	 * 加密模式之CBC
	 *
	 * @param key
	 * @param data
	 * @return
	 * @throws Exception
	 * @explain
	 */
	private static byte[] encryptCbcPadding(byte[] key, byte[] data) throws Exception {
		Cipher cipher = generateCbcCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.ENCRYPT_MODE, key);
		return cipher.doFinal(data);
	}

	/**
	 * sm4解密
	 *
	 * @param hexKey 16进制密钥
	 * @param text   16进制的加密字符串（忽略大小写）
	 * @return 解密后的字符串
	 * @throws Exception
	 * @explain 解密模式：采用CBC
	 */
	public static String decrypt(String hexKey, String text) throws Exception {
		// 用于接收解密后的字符串
		String result = "";
		// hexString-->byte[]
		byte[] keyData = ByteUtils.fromHexString(hexKey);
		// hexString-->byte[]
		byte[] resultData = ByteUtils.fromHexString(text);
		// 解密
		byte[] srcData = decryptCbcPadding(keyData, resultData);
		// byte[]-->String
		result = new String(srcData, ENCODING);
		return result;
	}

	public static String decrypt(String text) {
		try{
			if(StringUtils.isBlank(text)){
				return text;
			}
			return decrypt(generateKeyString(), text);
		}catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public static String encrypt(String text) {
		try{
			if(StringUtils.isBlank(text)){
				return text;
			}
			return encrypt(generateKeyString(), text);
		}catch (Exception e) {
			throw new RuntimeException(e);
		}
	}



	public static String encryptForJobtask(String text){
		if(StringUtils.isBlank(text)){
			return text;
		}
		try{
			//如果是明文解密肯定报错，如果报错之前肯定不是密文，防止对字段进行二次加密
			decrypt(text);
			//如果没有报错就返回原文，就证明是密文
			return text;
		}catch (Exception e) {
			return encrypt(text);
		}
	}

	/**
	 * 解密
	 *
	 * @param key
	 * @param cipherText
	 * @return
	 * @throws Exception
	 * @explain
	 */
	private static byte[] decryptCbcPadding(byte[] key, byte[] cipherText) throws Exception {
		Cipher cipher = generateCbcCipher(ALGORITHM_NAME_CBC_PADDING, Cipher.DECRYPT_MODE, key);
		return cipher.doFinal(cipherText);
	}

	public static void main(String[] args) throws Exception {
		String str = "邱力军";
		System.out.println("==========生成密钥==========");
		String generateKey = generateKeyString();
		System.out.println(generateKey);
		System.out.println("==========加密==========");
		String encrypt = encrypt( str);
		System.out.println(encrypt);
		System.out.println("==========解密==========");
		String decrypt = decrypt( "26ea703788d9220da8e2d858c67babdb");
		System.out.println(decrypt);
	}
}